import ms from 'ms';
import { pathOr } from 'ramda';

import fetch from 'fermionjs/lib/fetch';
import urlPathConcat from '@kuiper-helpers/url-path-concat/dist';

import pkg from '../../../../package.json';
import {
	AVILIABLE_TASK_MAP,
	JOB_TASK_TYPE_MAP,
} from '../../../common/constants';
import errorCodesBuilder from '../../helpers/errorCodesBuilder';
import aviliable from './responseParser/aviliable';
import vul from './responseParser/vul';
import ssl from './responseParser/ssl';
import securityEvent from './responseParser/securityEvent';
import asset from './responseParser/asset';
import alert from './alert';
import warningCounts from '../../helpers/warningCounts';

export const INTERVAL = ms('1m');
const errorCodes = errorCodesBuilder('任务执行失败');

export async function handleTaskCompleted(server, { task, result }) {
	const {
		app: {
			config: {
				[pkg.name]: { serviceApi, token },
			},
			model: { Task, Asset, Job, ResultNumbers },
		},
	} = server;
	const { _id: recordId, taskType } = task;

	server.log.debug({ recordId, taskType }, '任务执行完成：更新Mongo');

	let ref;
	let job;
	try {
		task.isSpecialTask = task.triggerType === 'special';
		task.result = result;
		task.isLatestTask = true;
		await Task.updateMany(
			{
				taskType: task.taskType,
				isLatestTask: true,
				_id: { $ne: task._id },
				refId: task.refId,
				triggerType: { $ne: 'special' },
			},
			{ $set: { isLatestTask: false } }
		);
		ref = job = await Job.findOne({ _id: task.jobId });
		if (taskType === 'asset') {
			ref = await Asset.findOne({ _id: task.refId });
		}
	} catch (err) {
		const error = errorCodes['[01]查询Mongo任务记录失败'].from(err);
		server.log.error({ error, taskType, recordId, result }, error.message);
		throw error;
	}

	if (!ref) {
		server.log.warn(
			{ refId: task.refId, taskType: task.taskType, _id: task._id },
			'处理任务完成逻辑：未查询到关联的Ref记录'
		);
		task.remove();
		return task;
	}

	let taskIdx = task.taskIdx;
	if (task.taskType === 'http') {
		taskIdx = ref.httpSettings.findIndex(x => x.target === task.target);
	}

	const warnings = result?.warnings || [];
	task.securityStatus = warnings.length > 0 ? 'warning' : 'safe';
	if (task.refType === 'job') {
		task.result.warnings = warnings.map(x => {
			if (ref.ignoredWarnIdMap[x.id]) {
				x.ignoredTime = ref.ignoredWarnIdMap[x.id];
			}
			return x;
		});
	}
	if (
		warnings.length > 0 &&
		// 目前不会有 isResolved 为 true 的
		warnings.every(x => x.isResolved || x.ignoredTime)
	) {
		task.securityStatus = 'safe';
	}
	Object.assign(task.result, warningCounts(warnings));

	// 更新任务配置记录securityStatus字段
	if (JOB_TASK_TYPE_MAP[taskType]) {
		if (AVILIABLE_TASK_MAP[taskType]) {
			if (!ref[`${task.taskType}SecurityStatus`]) {
				ref[`${task.taskType}SecurityStatus`] = [];
			}
			ref[`${task.taskType}SecurityStatus`].splice(
				task.taskIdx,
				1,
				task.securityStatus
			);
		} else if (task.triggerType !== 'special') {
			ref[`${task.taskType}SecurityStatus`] = task.securityStatus;
		}
	} else {
		// Asset
		ref.securityStatus = task.securityStatus;
	}

	// 更新job/asset中isStarted字段
	if (JOB_TASK_TYPE_MAP[taskType]) {
		if (AVILIABLE_TASK_MAP[taskType]) {
			if (!ref[`${task.taskType}IsStarted`]) {
				ref[`${task.taskType}IsStarted`] = [];
			}
			ref[`${task.taskType}IsStarted`].splice(task.taskIdx, 1, false);
		} else {
			ref[`${task.taskType}IsStarted`] = false;
		}
	} else {
		ref.isStarted = false;
	}

	let resultNumbers = warnings.reduce(
		(obj, item) => {
			if (item.level === 1) {
				obj.lowWarnCount++;
			} else if (item.level === 2) {
				obj.mediumWarnCount++;
			} else {
				obj.highWarnCount++;
			}
			return obj;
		},
		{
			highWarnCount: 0,
			mediumWarnCount: 0,
			lowWarnCount: 0,
		}
	);
	if (task.triggerType !== 'special') {
		if (taskType === 'asset') {
			const prevResultId = ref.resultId;
			ref.resultId = task.id;
			if (prevResultId) ref.prevResultId = prevResultId;
		} else if (AVILIABLE_TASK_MAP[taskType]) {
			if (!ref[`${task.taskType}ResultId`]) {
				ref[`${task.taskType}ResultId`] = [];
			}
			const prevResultId = ref[`${taskType}ResultId`][taskIdx];
			ref[`${taskType}ResultId`].splice(taskIdx, 1, task.id);
			ref[`${taskType}Abnormal`].splice(
				taskIdx,
				1,
				task.securityStatus === 'warning'
			);

			resultNumbers = result?.addition?.detail?.reduce(
				(res, item) => {
					if (item.securityStatus === 'warning') {
						res.abnorNodeCount++;
					} else {
						res.normalNodeCount++;
					}
					res.totalNodeCount++;
					return res;
				},
				{
					...resultNumbers,
					abnorNodeCount: 0,
					normalNodeCount: 0,
					totalNodeCount: 0,
				}
			);
			try {
				await new ResultNumbers({
					taskType,
					uid: task.uid,
					jobId: ref._id,
					taskName: task.name,
					target: task.target,
					datetime: task.endTime,
					taskId: task._id,
					warnCount: warnings.length,
					aviliable: result?.addition?.detail,
					...resultNumbers,
				}).save();
			} catch (err) {
				const error = errorCodes['[01]新增可用性任务数据失败'].from(err);
				server.log.error({ error, taskType, recordId, result }, error.message);
				throw error;
			}

			if (prevResultId) {
				ref[`${taskType}PrevResultId`].splice(taskIdx, 1, prevResultId);
			}
		} else {
			const prevResultId = ref[`${taskType}ResultId`];
			ref[`${taskType}ResultId`] = task.id;
			ref[`${taskType}Abnormal`] = task.securityStatus === 'warning';

			try {
				await new ResultNumbers({
					taskType,
					uid: task.uid,
					jobId: ref._id,
					taskName: task.name,
					target: task.target,
					datetime: task.endTime,
					taskId: task._id,
					warnCount: warnings.length,
					...resultNumbers,
				}).save();
			} catch (err) {
				const error = errorCodes['[01]新增非可用性任务数据失败'].from(err);
				server.log.error({ error, taskType, recordId, result }, error.message);
				throw error;
			}

			if (prevResultId) ref[`${taskType}PrevResultId`] = prevResultId;
		}
	}

	try {
		if (
			task.triggerType === 'schedule' ||
			task.triggerType === 'scheduleIncrement' ||
			task.taskSettings.enableSitePortraitTriggerMonitor
		) {
			task.alertSettings = job.alertSettings;
			if (!job.enableAlert) task.alertSettings.enable[task.taskType] = false;
		}

		const taskAlertEnable = task.taskSettings.alert?.enable;
		if (
			taskAlertEnable !== undefined &&
			(task.triggerType === 'manual' || task.triggerType === 'special')
		) {
			task.alertSettings.enable[task.taskType] = taskAlertEnable;
		}

		task.status = 'completed';
		await task.save();
		await ref.save();
	} catch (err) {
		const error = errorCodes['[01]更新结果失败'].from(err);
		server.log.error(
			{ error, task, taskType, recordId },
			error.internalMessage
		);
	}

	setImmediate(async () => {
		await alert({
			task,
			ref,
			job,
			server,
			taskIdx,
		});

		const ops = [];
		const acc =
			pathOr(1, ['result', 'addition', 'statistics', 'urls'], task) * -1;
		switch (task.taskType) {
			case 'vul':
			case 'securityEvent':
				ops.push(
					{
						key: `${task.taskType}MaxPage`,
						value: acc,
					},
					{
						key: 'maxPage',
						value: acc,
					}
				);
				break;
		}
		server.methods.calcBalance({ ops, uid: task.uid });

		if (task.taskType === 'vul' || task.taskType === 'securityEvent') {
			const yunaqBlacklistUrl = urlPathConcat(
				serviceApi,
				'/api/v2/cloud/udb_system_add_blacklist'
			);
			fetch(yunaqBlacklistUrl, {
				method: 'POST',
				headers: {
					cookie: `token=${token}`,
				},
				body: JSON.stringify({
					taskIds: [task._id],
				}),
			})
				.then(resp =>
					server.log.info(
						{
							resp,
							api: yunaqBlacklistUrl,
							taskType: task.taskType,
							taskId: task._id,
						},
						'加黑请求发送成功'
					)
				)
				.catch(error =>
					server.log.error(
						{
							error,
							api: yunaqBlacklistUrl,
							taskType: task.taskType,
							taskId: task._id,
						},
						'加黑请求发送失败'
					)
				);
		}
		server.log.info(
			{ recordId, taskType: taskType, balanceOps: ops },
			'任务执行完成'
		);
	});
}

async function handleTaskFailed(server, { recordId, task }) {
	try {
		task.status = 'failed';
		task.result = {
			warnings: [],
		};
		await task.save();
	} catch (err) {
		const error = errorCodes['[01]更新结果失败'].from(err);
		server.log.error({ error, recordId }, error.internalMessage);
	}
}

export default async function parseTaskResult(server) {
	const {
		log,
		app: {
			model: { Task, Asset },
		},
	} = server;

	log.info('处理任务结果开始');

	let count = 0;
	for await (const task of Task.find({
		progress: 100,
		status: 'active',
		securityStatus: 'unknown',
		taskType: { $ne: 'content' },
	})) {
		let ret = {};
		const target = task.target;
		const taskSettings = task.taskSettings;
		const response = task.result?.addition;
		if (!response) {
			log.warning({ taskId: task._id, result: task.result }, '任务结果为空');
			// continue;
		}

		try {
			switch (task.taskType) {
				case 'ping':
				case 'http':
					ret = aviliable({
						target,
						taskSettings,
						response,
						task,
					});
					break;
				case 'vul':
					ret = vul({
						taskSettings,
						response,
					});
					break;
				case 'ssl':
					ret = ssl({
						taskSettings,
						response,
						target,
					});
					break;
				case 'securityEvent':
					ret = securityEvent({
						taskSettings,
						response,
						target,
					});
					break;
				case 'asset':
					ret = await asset({
						log,
						Asset,
						target,
						taskSettings,
						response,
						assetId: task.refId,
					});
					break;
				default:
					ret = task.result;
			}
		} catch (error) {
			server.log.error({ error, ret }, '任务结果处理失败');
			continue;
		}

		// 处理老数据中的unit
		if (task.taskSettings?.interval?.unit) {
			const { num, unit } = task.taskSettings.interval;
			task.taskSettings.interval = ms(`${num}${unit}`);
		}

		try {
			if (task.status === 'failed') {
				await handleTaskFailed(server, {
					task,
					recordId: task._id,
				});
			} else {
				await handleTaskCompleted(server, {
					task,
					result: ret,
				});
			}
		} catch (error) {
			server.log.error({ error, ret }, '处理任务完成逻辑失败');
		}
		count += 1;
	}

	log.info({ count }, '处理任务结果完成');
}
